/****
 *
 * $Id: file.c,v 1.9 2009/09/29 23:16:58 rdilley Exp $
 *
 * Copyright (c) 2009, Ron Dilley
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   - Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   - Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in
 *     the documentation and/or other materials provided with the
 *     distribution.
 *   - Neither the name of Uberadmin/BaraCUDA/Nightingale nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 ****/

/****
 *
 * Utility functions
 *
 ****/

/****
 *
 * includes
 *
 ****/

#include "file.h"

/****
 *
 * local variables
 *
 ****/

PRIVATE char *cvsid = "$Id: file.c,v 1.9 2009/09/29 23:16:58 rdilley Exp $";

/****
 *
 * global variables
 *
 ****/

struct hash_s *uhHash;
struct hash_s *khHash;
FILE *dictionaryFile = NULL;
struct knownPlainText_s *knownPlainTextList = NULL;

/* structs to hold plaintext generation state */
pureBruteForceState_t pbfState;

/****
 *
 * external globals
 *
 ****/

extern int totalGuessed;
extern char *potFileName;
extern int quit;
extern int debug;

/****
 *
 * functions
 *
 ****/

/****
 *
 * save guessed hash/password pair
 *
 ****/

int saveGuessedHash( workUnit_t *workUnit ) {
  FILE *outFile;
  char hBuf[4096];
  char ptBuf[4096];

  if ( ( outFile = fopen( potFileName, "a" ) ) EQ NULL ) {
    die("ERR - Unable to open [%s] for updates\n", potFileName );
    return FAILED;
  }

  fprintf( outFile, "$NT$%s:%s\n", hexConvert( workUnit->uHash, workUnit->uhLen, hBuf, sizeof( hBuf ) ), utfConvert( workUnit->pt, workUnit->ptLen, ptBuf, sizeof( ptBuf ) ) );

  fclose( outFile );

  return TRUE; 
}

/****
 *
 * load precomputed hashes - if we know it, don't crack it
 *
 ****/

int loadKnownHashes( const char *fName ) {
  FILE *inFile;
  int i, j;
  char rLine[MAX_RBUF_LEN];
  char tHash[MAX_HASH_LEN];
  char hByte[3];
  char pt[MAX_PLAINTEXT_LEN+1];
  hData_t *hdPtr;
  char *ptPtr;
  struct hashRec_s *hPtr;
  char tBuf[MAX_RBUF_LEN];
  struct knownPlainText_s *tmpPtRec;

  if ( fName == NULL ) {
    //die("ERR - Filename is NULL\n" );
    return (-1);
  }

  if ( strlen( fName ) == 0 ) {
    die("ERR - Filename is empty\n" );
    return (-1);
  }

  if ( strlen( fName ) > MAX_PATH_LEN ) {
    die("ERR - Filename is too long\n" );
    return (-1);
  }

  if ( debug >= 1 )
    fprintf( stderr, "Loading known hashes from [%s]\n", fName );

  if ( ( inFile = fopen( fName, "r" ) ) == NULL ) {
    //die("ERR - Unable to open hash file [%s]\n", fName );
    return (-1);
  }

  while( fgets( rLine, MAX_RBUF_LEN, inFile ) != NULL ) {

    /* XXX making assumption about format is BAD */

    if ( (rLine[0] EQ '$') & (rLine[1] EQ 'N') & (rLine[2] EQ 'T') & (rLine[3] EQ '$') & (rLine[36] EQ ':') ) {
      /* NTLM Hash */
      /* convert hash string to binary */
      for( i = 0, j = 4; i < MAX_HASH_LEN; i++, j+=2 ) {
	hByte[0] = rLine[j];
	hByte[1] = rLine[j+1];
	tHash[i] = strtoul( hByte, NULL, 16 );
      }
      ptPtr = rLine+37; /* XXX this is really bad, should not assume formatting */

      /* add plaintext to list for learning */
      if ( ( tmpPtRec = (struct knownPlainText_s *)XMALLOC( sizeof( struct knownPlainText_s ) ) ) EQ NULL )
	die( "ERR - Unable to allocate memory for plain text structure\n" );
      bzero( tmpPtRec, sizeof( struct knownPlainText_s ) );
      for( i = 0; ptPtr[i] != '\0' & i < MAX_PASSWORD_LEN; i++ )
	if ( ptPtr[i] EQ '\n' | ptPtr[i] EQ '\r' )
	  ptPtr[i] = '\0';
      XSTRNCPY( tmpPtRec->pt, ptPtr, MAX_PASSWORD_LEN );
      tmpPtRec->ptLen = strlen( tmpPtRec->pt );
      /* add it to the top of the stack */
      tmpPtRec->next = knownPlainTextList;
      knownPlainTextList = tmpPtRec;

      /* search unknowns for matches */
      if ( ( hPtr = snoopHashRecord( uhHash, tHash, MAX_HASH_LEN ) ) != NULL ) {
	/* no need to crack this one, we already cracked it */
	totalGuessed++;
	for ( i = 0; (i < MAX_PLAINTEXT_LEN) & (ptPtr[i] != '\0'); i++ )
	  if ( ptPtr[i] EQ '\n' )
	    ptPtr[i] = '\0';

	if ( hPtr->data EQ NULL ) {
	  /* no hash data stored */
	  if ( ( hPtr->data = XMALLOC( sizeof( hData_t ) ) ) EQ NULL ) {
	    die("Unable to allocate memory for hash data\n" );
	    return (-1);
	  }
	  hdPtr = (hData_t *)hPtr->data;
	  XMEMSET( hdPtr, 0, sizeof( hData_t ) );
	  XMEMCPY( hdPtr->h, tHash, MAX_HASH_LEN );
	  hdPtr->hLen = NTLM_HASH_LEN;
	  XSTRNCPY( hdPtr->pt, ptPtr, MAX_PLAINTEXT_LEN );
#ifdef DEBUG
	  if ( debug >= 7 )
	    fprintf( stderr, "%s [%s]\n", hash2hex( tHash, tBuf, MAX_HASH_LEN ), hdPtr->pt );
#endif
	} else {
	  /* unknown has already has hash data, collision */
	  hdPtr = (hData_t *)hPtr->data;
#ifdef DEBUG
	  if ( debug >= 7 )
	    fprintf( stderr, "%s [%s] - %s [%s]\n", hash2hex( (char *)tHash, tBuf, MAX_HASH_LEN ), ptPtr, hash2hex( (char *)hdPtr->h, tBuf+64, MAX_HASH_LEN ), hdPtr->pt );
#endif
	}
      }
    } else {
      /* unknown hash */
      fprintf( stderr, "Unknown hash [%s]\n", rLine );
    }
  }

  /* done with file, close it */
  fclose( inFile );

  printf( "Skipping %d hashes that have already been cracked\n", totalGuessed );

  /* train the plaintext generators */
  learnFromPlaintexts( knownPlainTextList );

  return TRUE;
}

/****
 *
 * load target hashes
 *
 ****/

int loadUnknownHashes( const char *fName ) {
  FILE *inFile;
  int hCount = 0, i, j, count;
  char rLine[MAX_RBUF_LEN];
  char tHash[MAX_HASH_LEN];
  char hByte[3];
  struct hashList_s *hHead = NULL, *hPtr;

  if ( fName == NULL ) {
    die("ERR - Filename is NULL\n" );
    return (-1);
  }

  if ( strlen( fName ) == 0 ) {
    die("ERR - Filename is empty\n" );
    return (-1);
  }

  if ( strlen( fName ) > MAX_PATH_LEN ) {
    die("ERR - Filename is too long\n" );
    return (-1);
  }

  if ( ( inFile = fopen( fName, "r" ) ) == NULL ) {
    die("ERR - Unable to open hash file [%s]\n", fName );
    return (-1);
  }

  while( fgets( rLine, MAX_RBUF_LEN, inFile ) != NULL ) {

    /* XXX making assumption about format is BAD */

    for( i = 0, j = 0; i < MAX_HASH_LEN; i++, j+=2 ) {
      hByte[0] = rLine[j];
      hByte[1] = rLine[j+1];
      tHash[i] = strtoul( hByte, NULL, 16 );
    }

    if ( ( hPtr = (struct hashList_s *)XMALLOC( sizeof(struct hashList_s) ) ) == NULL ) {
      fprintf( stderr, "ERR - Unable to allocate memory for hash\n" );
      return (-1);
    }
    XMEMSET( hPtr, 0, sizeof( struct hashList_s ) );

    if ( hHead EQ NULL )
      hHead = hPtr;
    else {
      hPtr->next = hHead;
      hHead = hPtr;
    }

    XMEMCPY( hPtr->hValue, tHash, i );
    hPtr->len = i;
    hPtr->type = HASH_TYPE_MD4; /* XXX should be configurable */

    hCount++;
  }

  /* done with file, close it */
  fclose( inFile );

  /* initialize hash to just the right size */
  uhHash = initHash( hCount ); 

  /* move data from linked list to hash */
  hPtr = hHead;
  count = 0;
  while( hPtr != NULL ) {
    if ( addUniqueHashRec( uhHash, (char *)hPtr->hValue, MAX_HASH_LEN, NULL ) EQ FAILED ) {
      count++;
      //fprintf( stderr, "Problem hashing record\n" );
    }
    hPtr = hPtr->next;
  }

  if ( debug >= 1 )
    fprintf( stderr, "Read %d unknown hashes, Loaded %d hashes, %d problems\n", hCount, uhHash->totalRecords, count );

  return hCount;
}

/****
 *
 * open dictionary file
 *
 ****/

int openPwDict( const char *fName ) {
  if ( dictionaryFile != NULL ) {
    die("ERR - Dictionary file already open\n" );
    return FAILED;
  }

  if ( ( dictionaryFile = fopen( fName, "r" ) ) EQ NULL ) {
    die("ERR - Unable to open dictionary file [%s]\n", fName );
    return FAILED;
  }

  return TRUE;
}

/****
 *
 * read plaintexts from dictionary
 *
 ****/

int loadPwDict( workUnit_t *workUnits, int loadCount ) {
  char rBuf[4096];
  int i = 0;

  if ( dictionaryFile EQ NULL ) {
    die("ERR - Need to open dictionary file first\n" );
    return FAILED;
  }

  /* continue reading from the dictionary file until count or EOF */
  while( i++ < loadCount ) {
    if ( fgets( rBuf, sizeof( rBuf ), dictionaryFile ) EQ NULL ) {
      /* EOF */
      fclose( dictionaryFile );
      return i+1;
    }
    printf( "%s", rBuf );
  }
  return i+1;
}

/****
 *
 * save state
 *
 ****/

int saveState( char *fName ) {
  FILE *outFile;

  fprintf( stderr, "Saving crack session [%s], use -s to resume\n", fName );

  if ( ( outFile = fopen( fName, "w" ) ) EQ NULL )
    die( "ERR - Unable to open crack state file [%s]\n", fName );

  /* save all state structures */

  /* pure brute force */
  if ( fwrite( &pbfState, sizeof( pureBruteForceState_t ), 1, outFile ) != 1 )
    die( "ERR - Unable to write crack state to [%s]\n", fName );

  /* blah blah */


  /* done, clean up */
  fclose( outFile );

  return TRUE;
}

/****
 *
 * load state from file if we can
 *
 ****/

int loadState( char *fName ) {
  FILE *inFile;

  if ( ( inFile = fopen( fName, "r" ) ) EQ NULL )
    return FAILED;

  /* load all state structures */

  /* XXX should not assume that the data is sane */

  /* pure brute force */
  if ( fread( &pbfState, sizeof( pureBruteForceState_t ), 1, inFile ) != 1 )
    return FAILED;

  /* blah blah */

  /* done, clean up */
  fclose( inFile );

  return TRUE;
}

/****
 *
 * learn from known plaintexts
 *
 ****/

/* sorters */
struct bTreeNode_s {
  char c;
  int count;
  struct bTreeNode_s *less;
  struct bTreeNode_s *more;
};

/* frequency based token list */
struct ptTokens_s {
  unsigned char tokens[MAX_PASSWORD_LEN][256];
};
struct ptTokens_s ptTokens[MAX_PASSWORD_LEN];

/* counters */
struct ptStats_s {
  unsigned int tokens[MAX_PASSWORD_LEN][256];
  struct bTreeNode_s *top;
};

int learnFromPlaintexts( struct knownPlainText_s *plainTextList ) {
  struct knownPlainText_s *tmpPtRec = plainTextList;
  int i, j, n, c, curPos;
  struct ptStats_s ptStats[MAX_PASSWORD_LEN];

  /* clear the counters */
  for( i = 0; i < MAX_PASSWORD_LEN; i++ ) {
    ptStats[i].top = NULL;
    for( j = 0; j < MAX_PASSWORD_LEN; j++ )
      for( n = 0; n < 256; n++ )
	ptStats[i].tokens[j][n] = 0;
  }

  /* count 'em */
  while( tmpPtRec != NULL ) {
#ifdef DEBUG
    fprintf( stderr, "counting [%s]\n", tmpPtRec->pt );
#endif
    for( i = 0; i < tmpPtRec->ptLen & i < MAX_PASSWORD_LEN; i++ ) {
      ptStats[tmpPtRec->ptLen].tokens[i][tmpPtRec->pt[i]]++;
    }

    tmpPtRec = tmpPtRec->next;
  }

  /* sort 'em */
  for( i = 0; i < MAX_PASSWORD_LEN; i++ ) {
    for( j = 0; j < i; j++ ) {
      fprintf( stderr, "len: %d pos: %d ", i, j );
      for( n = 0; n < 256; n++ ) {
	if ( ptStats[i].tokens[j][n] > 0 ) {
	  /* this token has been used */
	  fprintf( stderr, "%c=%d ", n, ptStats[i].tokens[j][n] );
	  //if ( ptStats[i].top EQ NULL ) {
	    /* first node */
	  //  if ( ( nodePtr = XMALLOC( sizeof( struct bTreeNode_s ) ) ) EQ NULL )
	  //    die( "ERR - Unable to allocate memory for btree node\n" );
	  //  bzero( nodePtr, sizeof( struct bTreeNode_s ) );
	  //  nodePtr->c = n;
	  //  nodePtr->count = ptStats[i].tokens[j][n];
	  //  ptStats[i].top = nodePtr;
	  //} else {
	  //  
	  //}
	}
      }
      fprintf( stderr, "\n" );
    }
  }

  
  return TRUE;
}
